package app.server.project.main.service.service.withdraw;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.exception.SysError;
import app.server.project.core.http.CMSLstResp;
import app.server.project.core.util.AssertUtil;
import app.server.project.core.util.MathUtil;
import app.server.project.core.util.SysTimeUtil;
import app.server.project.core.websocket.WSMgr;
import app.server.project.main.service.bo.dbquery.WithDrawParam;
import app.server.project.main.service.bo.event.ConsumeRaceValEvent;
import app.server.project.main.service.bo.event.PassEvent;
import app.server.project.main.service.bo.event.RejectEvent;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.constants.LuckType;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.cfg.RaceCfgDao;
import app.server.project.main.service.db.dao.user.*;
import app.server.project.main.service.db.entity.cfg.RaceCfgEntity;
import app.server.project.main.service.db.entity.user.UserActiveRewardEntity;
import app.server.project.main.service.db.entity.user.UserInfoEntity;
import app.server.project.main.service.db.entity.user.UserWithDrawDetailEntity;
import app.server.project.main.service.dto.BalanceDto;
import app.server.project.main.service.dto.cms.QueryWithDrawReq;
import app.server.project.main.service.dto.withdraw.WithDrawReq;
import app.server.project.main.service.dto.withdraw.WithDrawStatusReq;
import app.server.project.main.service.dto.withdraw.WithDrawStatusResp;
import app.server.project.main.service.service.LockService;
import app.server.project.main.service.service.ResService;
import app.server.project.main.service.service.race.CoinType;
import app.server.project.main.service.service.race.DetailType;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.List;

@Service
@AllArgsConstructor
public class WithDrawService {


    private final static long LOGIN_DAY = 30;
    private final LuckType luckType;

    private UserRaceDao userRaceDao;
    private RaceCfgDao raceCfgDao;
    private GlobalCfgDao globalCfgDao;
    private ResService resService;
    private UserWithDrawDetailDao userWithDrawDetailDao;
    private LockService lockService;
    private UserExtDao userExtDao;
    private EnergyInfoDao energyInfoDao;
    private UserInfoDao userInfoDao;
    private ApplicationEventPublisher applicationEventPublisher;
    private UserActiveRewardDao userActiveRewardDao;


    public WithDrawStatusResp getWithDrawStatus(long userId, WithDrawStatusReq req) {
        WithDrawStatusResp withDrawStatusResp = new WithDrawStatusResp();
        var dbList = this.userRaceDao.getRaceInfo(userId);
        var cfg = this.raceCfgDao.getOne(req.getRaceId());
        var userInfo = this.userInfoDao.getUserInfoById(userId);
        var ext = this.userExtDao.getInfoById(userId);
        var dbOne = this.userRaceDao.getOne(dbList, req.getRaceId(), userId);


        var loginDay = LOGIN_DAY;
        if (this.globalCfgDao.has(GlobalCfgKey.WithDraw_Login_Day)) {
            loginDay = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
        }
        //登录天数
        withDrawStatusResp.setNeedCheckInDay((int) loginDay);


        if (CoinType.AI_COIN == req.getRaceId()) {
            //金额
            withDrawStatusResp.setAlreadyAmount(userInfo.getAiCoin());
        } else {
            withDrawStatusResp.setAlreadyAmount(dbOne.getVal());
        }
        withDrawStatusResp.setNeedAmount(cfg.getWithdrawAmount());
        withDrawStatusResp.setRate(cfg.getRate());

        //人数
        withDrawStatusResp.setNeedInviteNum(cfg.getInviteNumCfg());

        boolean status = false;

        if (req.getRaceId() == CoinType.BTC_COIN) {
            withDrawStatusResp.setAlreadyInviteNum((int) ext.getBtcUserNum());
            //连续登录天数
            //检查今天是否打过卡
            var userEnergy = this.energyInfoDao.getInfoById(userId);
            if (userEnergy.alwaysLogin()) {
                withDrawStatusResp.setAlreadyCheckInDay((int) userEnergy.getBtcDrawNum());
            } else {
                if (userEnergy.getBtcDrawNum() >= loginDay) {

                    if (userEnergy.getBtcDrawTime() > Def.Zero) {
                        var lastTime = DateUtil.toLocalDateTime(DateUtil.beginOfDay(new Date(userEnergy.getBtcDrawTime())));
                        var now = LocalDateTimeUtil.beginOfDay(SysTimeUtil.getAppTimeLocalTime());
                        var diffDay = Duration.between(lastTime, now);
                        if (diffDay.toDays() >= loginDay) {
                            withDrawStatusResp.setAlreadyCheckInDay(Def.Zero);
                        } else {
                            withDrawStatusResp.setAlreadyCheckInDay((int) (loginDay - diffDay.toDays()));
                        }
                    } else {
                        withDrawStatusResp.setAlreadyCheckInDay((int) (loginDay - 1));
                    }
                } else {
                    withDrawStatusResp.setAlreadyCheckInDay((int) userEnergy.getBtcDrawNum());
                }
            }

            status = ((withDrawStatusResp.getAlreadyCheckInDay() >= loginDay)
                    && (ext.getBtcUserNum() >= cfg.getInviteNumCfg())
                    && (this.resService.hasNum(userId,
                    cfg.getWithdrawAmount(),
                    req.getRaceId())));

        } else {
            withDrawStatusResp.setAlreadyInviteNum((int) ext.getInviteUserNum());
            status = (
                    (ext.getInviteUserNum() >= cfg.getInviteNumCfg())
                            && (this.resService.hasNum(userId,
                            cfg.getWithdrawAmount(),
                            req.getRaceId())));
        }


        withDrawStatusResp.setStatus(status);

        return withDrawStatusResp;
    }


    public void doWithDraw(WithDrawReq req, long userId) {
        var lock = this.lockService.getLock(userId);
        synchronized (lock) {
            var dbList = this.userRaceDao.getRaceInfo(userId);
            var cfg = this.raceCfgDao.getOne(req.getRaceId());
            var ext = this.userExtDao.getInfoById(userId);
            var dbOne = this.userRaceDao.getOne(dbList, req.getRaceId(), userId);
            var loginDay = LOGIN_DAY;
            if (this.globalCfgDao.has(GlobalCfgKey.WithDraw_Login_Day)) {
                loginDay = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
            }
            //提现开关
            AssertUtil.isTrue(cfg.getWithdrawStatus() == Def.True, SysError.WithDraw_Status);
            //检查提现金额
            AssertUtil.isTrue(this.resService.hasNum(userId,
                            cfg.getWithdrawAmount(),
                            req.getRaceId()),
                    SysError.WithDraw_Amount);
            //检查钱包地址
            AssertUtil.isTrue(StrUtil.isNotBlank(req.getWalletUrl()), SysError.WithDraw_Url);
            //检查提现邀请人数
            if (cfg.getId() == CoinType.BTC_COIN) {
                //比特币要检查有效用户数
                AssertUtil.isTrue(ext.getBtcUserNum() >= cfg.getInviteNumCfg(), SysError.Invite_BTC_NUM);
                var userEnergy = this.energyInfoDao.getInfoById(userId);
                AssertUtil.isTrue(userEnergy.alwaysLogin(), SysError.WithDraw_Time);
                AssertUtil.isTrue(userEnergy.getBtcDrawNum() >= loginDay, SysError.WithDraw_Time);
            } else {
                AssertUtil.isTrue(ext.getInviteUserNum() >= cfg.getInviteNumCfg(), SysError.WithDraw_Num);
            }


            var detail = UserWithDrawDetailEntity.of(IdUtil.getSnowflakeNextId());
            UserInfoEntity userInfoEntity = this.userInfoDao.getUserInfoById(userId);

            detail.setName(cfg.getName());
            detail.setNick(userInfoEntity.getNick());
            detail.setRaceId(cfg.getId());
            detail.setRate(cfg.getRate());
            detail.setStatus(WithDrawStatus.Apply);
            detail.setWalletLocation(req.getWalletUrl());
            detail.setUserId(userId);

            if (req.getRaceId() == CoinType.AI_COIN) {

                detail.setVal(userInfoEntity.getAiCoin());
                detail.setMoney(MathUtil.getNumFormat(userInfoEntity.getAiCoin(), cfg.getRate()));
                this.resService.consumeRaceNum(userId, userInfoEntity.getAiCoin(), req.getRaceId());

            } else {
                detail.setVal(dbOne.getVal());
                detail.setMoney(MathUtil.getNumFormat(dbOne.getVal(), cfg.getRate()));
                this.resService.consumeRaceNum(userId, dbOne.getVal(), req.getRaceId());

            }
            this.applicationEventPublisher.publishEvent(ConsumeRaceValEvent.of(detail.getUserId(), cfg, detail.getVal(), DetailType.Withdrawal.getType(), detail.getId()));
        }

    }

    public boolean doTonWithDraw(WithDrawReq req, long userId) {
        var lock = this.lockService.getLock(userId);

        //检查钱包地址
        AssertUtil.isTrue(StrUtil.isNotBlank(req.getWalletUrl()), SysError.WithDraw_Url);
        synchronized (lock) {
            UserActiveRewardEntity userActiveRewardEntity = this.userActiveRewardDao.getInfoById(userId);
            //检查提现金额
            AssertUtil.isTrue(userActiveRewardEntity.getTonVal() > Def.Zero, SysError.WithDraw_Amount);
            if (userActiveRewardEntity.getTonVal() > Def.Zero) {
                var money = userActiveRewardEntity.getTonVal();
                if (!userActiveRewardEntity.consumeTonVal(money)) return false;
                RaceCfgEntity cfg = new RaceCfgEntity();
                cfg.setId(CoinType.TON_COIN);
                cfg.setName("TON");
                cfg.setRate(LuckType.TonRate);

                WSMgr.push(userId, TcpCmd.TonVal, BalanceDto.of(String.valueOf(money), String.valueOf(cfg.getRate())));


                var detail = UserWithDrawDetailEntity.of(IdUtil.getSnowflakeNextId());
                UserInfoEntity userInfoEntity = this.userInfoDao.getUserInfoById(userId);

                detail.setName(cfg.getName());
                detail.setNick(userInfoEntity.getNick());
                detail.setRaceId(cfg.getId());
                detail.setRate(cfg.getRate());
                detail.setStatus(WithDrawStatus.Apply);
                detail.setWalletLocation(req.getWalletUrl());
                detail.setUserId(userId);

                detail.setVal(money);
                detail.setMoney(MathUtil.getNumFormat(money, cfg.getRate()));

                this.applicationEventPublisher.publishEvent(ConsumeRaceValEvent.of(detail.getUserId(), cfg, detail.getVal(), DetailType.Withdrawal.getType(), detail.getId()));
            }
        }
        return true;
    }

    public CMSLstResp<UserWithDrawDetailEntity> getAllByParam(QueryWithDrawReq req) {
        WithDrawParam withDrawParam = new WithDrawParam();
        BeanUtil.copyProperties(req, withDrawParam, true);
        if (withDrawParam.getStatus() == Def.Zero) {
            withDrawParam.setStatus(WithDrawStatus.Apply);
        }
        withDrawParam.setPageIndex(req.getPageIndex() + 1);
        var dbs = this.userWithDrawDetailDao.getAllByParam(withDrawParam);
        return CMSLstResp.of(dbs.getRecords(), dbs.getTotal());
    }

    public void pass(List<Long> ids) {
        this.userWithDrawDetailDao.pass(ids);
        var dbs = this.userWithDrawDetailDao.getDataById(ids);
        dbs.forEach(v -> {

            this.applicationEventPublisher.publishEvent(PassEvent.of(v.getId(), v.getUserId(), v.getRaceId()));


        });
    }

    public void reject(List<Long> ids) {
        this.userWithDrawDetailDao.reject(ids);
        var dbs = this.userWithDrawDetailDao.getDataById(ids);
        dbs.forEach(v -> {
            this.applicationEventPublisher.publishEvent(RejectEvent.of(v.getId(), v.getUserId(), v.getRaceId(), v.getVal()));
        });
    }
}
